﻿//-----------------------------------------------------------------------
// <copyright file="Program.cs" company="DylanBox">
// Copyright (c) DylanBox Corporation. All rights reserved.
// </copyright>
// <summary>
// Notes:
//  As soon as the main thread calls the asynchronous method, his part is done
//  The callback method is executed on the ThreadPool thread
//  The call to BeginInvoke (so far... it has been something like... caller.BeginInvoke(25, out threadId, null, null);) would now have the 3rd parameter as an AsyncCallback which contains the callback method name.
//  The 4th parameter takes an object which your callback method might like to use
//  The ThreadPool threads are background threads. This means that they won't keep the application running in case the main thread ends. Thus, the main thread has to be alive for long enough to ensure that the background threads are done processing.
// </summary>
// <reference>
//    http://www.dotnetscraps.com/dotnetscraps/post/Explaining-Delegates-in-C-Part-7-(Asynchronous-Callback-Way-4).aspx
// </reference>
// <author>Dylan</author>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Runtime.Remoting.Messaging;

namespace EventAndDelegateDemo
{
    //The delegate must have the same signature as the method. In this case,
    //we will make it same as TortoiseMethod
    public delegate string TortoiseCaller(int seconds, out int threadID);

    public class TortoiseClass
    {
        // The method to be executed asynchronously.
        public string TortoiseMethod(int seconds, out int threadID)
        {
            threadID = Thread.CurrentThread.ManagedThreadId;
            Console.WriteLine("The slow method... executes...on thread {0}", Thread.CurrentThread.ManagedThreadId);
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(seconds / 5 * 1000);
                Console.WriteLine("The async task is going on thread # {0}", Thread.CurrentThread.ManagedThreadId);
            }
            return String.Format("I worked in my sleep for {0} seconds", seconds.ToString());
        }
    }

    //Now, that we are done with the declaration part, let's proceed to
    //consume the classes and see it in action
    //The algorithm would be very simple...
    //         1. Call delegate's BeginInvoke and pass the callback method's name
    //         2. Do some work on the main thread
    //         3. Your callback method is called when the processing completes. 
    //         4. Retrieve the delegate and call EndInvoke which won't be a blocking call this time!
    public class TortoiseConsumer
    {
        static void Main()
        {
            //Instantiate a new TortoiseClass
            TortoiseClass tc = new TortoiseClass();
            //Let's create the delegate now
            TortoiseCaller caller = new TortoiseCaller(tc.TortoiseMethod);

            //This is a dummy variable since this thread is not supposed to handle the callback anyways!!!
            int dummy = 0;
            //Start the asynchronous call with the following parameters...
            //Parameter 1 = 30 > In my example the tortoise class will now do something for 30 seconds
            //Parameter 2 = Dummy variable, just to get the output of the threadID
            //Parameter 3 = new AsyncCallback(CallbackMethod) > This is the method which would be called once the async task is over
            //Parameter 4 = Object > This is a string which would display the information about the async call
            IAsyncResult result = caller.BeginInvoke(30, out dummy, new AsyncCallback(CallThisMethodWhenDone),
                "The call executed on thread {0}, with return value \"{1}\".");

            Console.WriteLine("The main thread {0} continues to execute...", Thread.CurrentThread.ManagedThreadId);

            //The callback method will use a thread from the ThreadPool.
            //But the threadpool threads are background threads. This implies that if you comment the line below
            //you would notice that the callback method is never called, since the background threads can't stop the main
            //program to terminate!
            Thread.Sleep(40000);
            Console.WriteLine("The main thread ends. Change the value 40000 in code to 3000 and see the result");
        }


        //The signature for the call back method must be same System.IAsyncResult delegate.
        static void CallThisMethodWhenDone(System.IAsyncResult ar)
        {
            //To retrieve the delegate we will cast the IAsyncResult to AsyncResult and get the caller
            AsyncResult result = (AsyncResult)ar;
            TortoiseCaller caller = (TortoiseCaller)result.AsyncDelegate;

            //Get the object (in our case it is just a format string) that was passed while calling BeginInvoke!  
            string formattedString = (string)ar.AsyncState;

            // Define a variable to receive the value of the out parameter.
            // If the parameter were ref rather than out then it would have to
            // be a class-level field so it could also be passed to BeginInvoke.
            //The following variable would take the Thread ID 
            int threadId = 0;

            //At this point, the threadID won't be a dummy variable as it was in the Main method
            //We are passing threadID to get the output from the TortoiseMethod
            string returnValue = caller.EndInvoke(out threadId, ar);

            //Use the format string to format the output message.
            Console.WriteLine(formattedString, threadId, returnValue);
        }
    }
}
